/**
 * @file streamsummarybuffer.cpp
 * @author Andreas Bok Andersen
 */

//#include <boost/ptr_container/ptr_vector.hpp>
#define STREAMSUMMARYBUFFER
//using namespace boost::ptr_container;

using namespace std;

typedef pair<size_t, Entry*> HASH_ENTRY_REF_PAIR;

class StreamSummaryBuffer: public Summary {

private:
	int b;
	HASH_ENTRY_REF_MAP *buffer;
	ENTRY_REF_VEC *summary;
	void merge();

public:
	StreamSummaryBuffer(int _k) :
			Summary(_k), b(_k) {
		this->buffer = new HASH_ENTRY_REF_MAP();
		this->summary = new ENTRY_REF_VEC();
	};

	~StreamSummaryBuffer();

	void insert(int i, int j, float weight);
	ENTRY_VEC get_summary();
	void get_summary(Entry entries[]);
	void get_summary(ENTRY_VEC *entries);
	string to_string();
	int summarysize();
};

StreamSummaryBuffer::~StreamSummaryBuffer() {
	ENTRY_REF_VEC::iterator it = this->summary->begin();
	for (; it != this->summary->end(); it++) {
		delete *it;
	}
	HASH_ENTRY_REF_MAP::iterator it2 = this->buffer->begin();
	for (; it2 != this->buffer->end(); it2++) {
		delete it2->second;
	}
	delete this->buffer;
	delete this->summary;
}

/**
 * @brief Merges the items in the summary and the items in buffer and keeps top k elements
 */
void StreamSummaryBuffer::merge() {
	//printf("%s\n", "merging");
	if (this->summary->size() == 0) {
		HASH_ENTRY_REF_MAP::iterator it = this->buffer->begin();
		for (; it != this->buffer->end(); it++) {
			this->summary->push_back(it->second);
		}
	} else {
		//printf("%s\n", "merging buffer and summary");
		BOOST_FOREACH(Entry * e, (*this->summary))
		{
			HASH_ENTRY_REF_MAP::iterator it = this->buffer->find(e->hash);
			if (it != this->buffer->end()) {
				it->second->weight += e->weight;
				it->second->eps += e->eps;
				delete e;
			} else {
				this->buffer->insert(HASH_ENTRY_REF_PAIR(e->hash, e));
			}
		}

		this->summary->resize(this->buffer->size());
		HASH_ENTRY_REF_MAP::iterator it = this->buffer->begin();
		int i = 0;
		for (; it != this->buffer->end(); it++) {
			this->summary->at(i) = it->second;
			i++;
		}
		//printf("%s # %d\n", "copied back into summary", summary->size());

		if (this->summary->size() > this->k) {
			sort(this->summary->begin(), this->summary->end(),
					sort_reverse_ref);
			// resize and delete old objects
			ENTRY_REF_VEC::iterator it = this->summary->begin() + this->k;
			for (; it != this->summary->end(); it++) {
				delete *it;
			}
			//printf("#Sum after delete %d; k %d\n ", summary->size(),this->k);

			this->summary->resize(this->k);
			//printf("#Sum after resize %d\n", summary->size());
		}
		//printf("%s\n", "sorted and resized");
	}

	this->buffer->clear();
	//printf("%s\n", "finished merge");
}

/**
 * Inserts a matrix element with index (i,j) and value weight
 * @param i row index
 * @param j column index
 * @param weight value of item
 */
void StreamSummaryBuffer::insert(int i, int j, float weight) {

	size_t _hash = hash_value(i, j);
	HASH_ENTRY_REF_MAP::iterator it = this->buffer->find(_hash);
	if (it != this->buffer->end()) {
		//printf("%s\n", "update in buffer");
		it->second->weight += weight;
	} else {
		//printf("%s\n", "insert in buffer");
		Entry * e = new Entry(i, j, weight, 0.0);
		this->buffer->insert(HASH_ENTRY_REF_PAIR(_hash, e));
		//printf("%s\n", "finished insert in buffer");
	}
	if (this->buffer->size() == this->b) {
		//printf("%s\n", "merge");
		merge();
	}
	//printf("#sum: %d #buf: %d\n", this->summary->size(), this->buffer->size());
}

/**
 * @return Returns summary as a vector of Entry
 */
ENTRY_VEC StreamSummaryBuffer::get_summary() {
	if (this->buffer->size() > 0)
		merge();
	std::auto_ptr < ENTRY_VEC > temp(new ENTRY_VEC);
	ENTRY_REF_VEC::iterator it = (*this->summary).begin();
	for (; it != (*this->summary).end(); it++) {
		(*temp).push_back(**it);
	}
	return (*temp);
}
/**
 * Fill entries into array of Entry. Argument is passed by reference
 * @param entries[] pointer to array of Entry
 */
void StreamSummaryBuffer::get_summary(Entry entries[]) {
	if (this->buffer->size() > 0)
		merge();
	ENTRY_REF_VEC::iterator it;
	for (int i = 0; i < this->summary->size(); i++) {
		(entries)[i] = *this->summary->at(i);
	}
}

/**
 * Fill entries into vector of Entry. Argument is passed by reference
 * @param entries pointer to vector<Entry>
 */
void StreamSummaryBuffer::get_summary(ENTRY_VEC * entries) {
	if (this->buffer->size() > 0)
		merge();
	ENTRY_REF_VEC::iterator it;
	for (it = this->summary->begin(); it != this->summary->end(); it++) {
		entries->push_back(*(*it));
	}
}

/**
 * @return Size of summary, bounded by k
 */
int StreamSummaryBuffer::summarysize() {
	if (this->buffer->size() > 0)
		merge();
	return this->summary->size();
}
/**
 * @return string representation of the streamsummary
 */
string StreamSummaryBuffer::to_string() {
	if (this->buffer->size() > 0)
		merge();
	sort(this->summary->begin(), this->summary->end(), sort_reverse_ref);
	stringstream ss;
	ENTRY_REF_VEC::iterator it = this->summary->begin();
	for (; it != this->summary->end(); it++) {
		ss << (*it)->to_string();
	}
	return ss.str();
}
